package com.sahyadri.web.util.tags;

import java.io.IOException;
import java.io.StringWriter;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.SimpleTagSupport;

import org.apache.log4j.Logger;
import org.springframework.util.StringUtils;

public class SimpleDataTableTag extends SimpleTagSupport implements OutputTypeAware {

	private static final String DEFAULT_SORT_ORDER = "YAHOO.widget.DataTable.CLASS_ASC";
	private static final String DEFAULT_ROWS_PER_PAGE_OPTIONS = "25,50,100,200";
	private static final String DEFAULT_TOTAL_RECORDS_FIELD = "totalRecords";
	private static final String DEFAULT_START_INDEX_FIELD = "startIndex";
	private static final String DEFAULT_PAGE_SIZE_FIELD = "pageSize";
	private static final String DEFAULT_SORT_COLUMN_FIELD = "sortColumn";
	private static final String DEFAULT_SORT_ORDER_FIELD = "sortDirection";
	private static final String DEFAULT_RESULT_LIST_FIELD = "queryResults";
	private static final String DEFAULT_STATUS_FIELD = "status";
	private static final String DEFAULT_PREVIOUS_PAGE_LINK_LABEL = "< previous";
	private static final String DEFAULT_NEXT_PAGE_LINK_LABEL = "next >";
	
	private static final String DEFAULT_INITIAL_LOAD = "false";
	private static final String DEFAULT_PAGE_REPORT_TEMPLATE = "Showing items {startRecord} - {endRecord} of {totalRecords}";
	
	private static final int DEFAULT_RESULTS_PER_PAGE = 25;
	
	private static final String NEW_LINE = "\n";
	
	private String id;
	private String dataSourceUrl;
	private String requestBuilder;
	private String doBeforeParseDataCallback;
	private String initialSortColumn;
	private String handlerButtonId;
	private String refreshPageButtonId;
	
	private String initialSortOrder = DEFAULT_SORT_ORDER;
	private String totalRecordsField = DEFAULT_TOTAL_RECORDS_FIELD;
	private String previousPageLinkLabel = DEFAULT_PREVIOUS_PAGE_LINK_LABEL;
	private String nextPageLinkLabel = DEFAULT_NEXT_PAGE_LINK_LABEL;
	
	// These are the fields that we will look in the json respons for to pull metadata
	private String resultListField = DEFAULT_RESULT_LIST_FIELD;
	private String startIndexField = DEFAULT_START_INDEX_FIELD;
	private String pageSizeField = DEFAULT_PAGE_SIZE_FIELD;
	private String sortColumnField = DEFAULT_SORT_COLUMN_FIELD;
	private String sortDirectionField = DEFAULT_SORT_ORDER_FIELD;
	private String statusField = DEFAULT_STATUS_FIELD;
	
	private int resultsPerPage = DEFAULT_RESULTS_PER_PAGE;
	private boolean displayRowsPerPageDropDown;
	private String rowsPerPageOptions = DEFAULT_ROWS_PER_PAGE_OPTIONS;
	private String initialLoad = DEFAULT_INITIAL_LOAD;
	private String pageReportTemplate = DEFAULT_PAGE_REPORT_TEMPLATE;
	private String initialRequest;
	
	private String rowFormatterCallback;
	
	// Callback handler for cell click events
	private String onCellClick;
	private String onRowSelect;
	
	Logger log = Logger.getLogger(this.getClass());

	// marker var used to tell child column tags which type of js to output
	private OutputType currentOutputType;
	
	private StringBuilder tagContent;
	
	@Override
	public void doTag() throws JspException, IOException {
		
		initializeTagContent();
		
		writeLine("<div id='" + id + "'></div>");
		
		writeLine("<script>");
		writeLine("var initDataTable = function() {");
		writeLine("var dataTableColumnDefs = [");

		// Delegate down to child column tags to construct column definition js.
		// Set OutputType to COLUMN_DEF so child column tags know which type of js to output
		writeLine(getChildMarkupByType(OutputType.COLUMN_DEF));;
		
		writeLine("];");
		
		writeLine("var myDataSource = new YAHOO.util.DataSource('" + dataSourceUrl + "');");
		// If present, register client callback with datasource to give them access to raw response if needed.
		if (StringUtils.hasText(doBeforeParseDataCallback)) {
			writeLine("myDataSource.doBeforeParseData = " + doBeforeParseDataCallback + ";");;
		}
		
		writeLine("myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;");;
		writeLine("myDataSource.responseSchema = {");;
		writeLine("resultsList: '" + resultListField + "',");;
		writeLine("fields: [");;

		// Delegate down to child column tags to construct schema definition js.
		// Set OutputType to SCHEMA_DEF so child column tags know which type of js to output
		writeLine(getChildMarkupByType(OutputType.SCHEMA_DEF));
		
		writeLine("],");
		writeLine("metaFields:  {");
		writeLine("  totalRecords:'" + totalRecordsField + "',");
		writeLine("  status:'" + statusField + "',");
		writeLine("  paginationRecordOffset:'" + startIndexField + "',");
		writeLine("  paginationRowsPerPage:'" + pageSizeField + "',");
		writeLine("  sortKey:'" + sortColumnField + "',");
		writeLine("  sortDir:'" + sortDirectionField + "'");
		writeLine("}");
		writeLine("};");
		
		// Create the Paginator
	    writeLine("var myPager = new YAHOO.widget.Paginator({");
	    String paginatorTemplate = "{CurrentPageReport} {PreviousPageLink} {PageLinks} {NextPageLink}";
	    if (displayRowsPerPageDropDown) {
	    	paginatorTemplate += "  <span class=yui-pg-rpp-options-label>Results per page:</span>  {RowsPerPageDropdown}";
	    }
	    writeLine("template : '" + paginatorTemplate + "',");
	    //TODO surface alwaysVisible as configurable attribute.
	    writeLine("alwaysVisible : false,");
	    //TODO surface pageReportTemplate as config attribute.
	    writeLine("pageReportTemplate : '" + pageReportTemplate  + "',");
	    if (displayRowsPerPageDropDown) {
	    	writeLine("rowsPerPageOptions : [" + rowsPerPageOptions + "],");
	    }
	    writeLine("previousPageLinkLabel : '" + previousPageLinkLabel + "',");
	    writeLine("nextPageLinkLabel : '" + nextPageLinkLabel + "',");
	    writeLine("rowsPerPage : " + resultsPerPage);
	    writeLine("});");
		
		
		// DataTable Config Definition
		writeLine("var dataTableConfigs = { \n");
		// If row formatter callback was specified, register it.
		if (rowFormatterCallback != null) {
			writeLine("  formatRow : " + rowFormatterCallback + ", \n");
		}
		writeLine("  paginator : myPager,");
		writeLine("  sortedBy : {");
		writeLine("    key: '" + initialSortColumn + "',");
		writeLine("    dir: '" + initialSortOrder + "'");
		writeLine("  },");
		writeLine("  dynamicData : true,");
		writeLine("  generateRequest : " + requestBuilder + ",");
		writeLine("  initialLoad : " + initialLoad + ",");
		writeLine("  initialRequest:  \"" + initialRequest + "\"");
		writeLine("};");
	    
		writeLine("var myDataTable = new YAHOO.widget.DataTable('" + id + "', dataTableColumnDefs, myDataSource, dataTableConfigs);");
		
		// Row highlighting
		writeLine(" myDataTable.subscribe(\"rowMouseoverEvent\", myDataTable.onEventHighlightRow);");
		writeLine(" myDataTable.subscribe(\"rowMouseoutEvent\", myDataTable.onEventUnhighlightRow);");
		
		// Add onRowSelect handler if specified
		if(StringUtils.hasText(onRowSelect)) {
			writeLine(" myDataTable.subscribe(\"rowClickEvent\", " + onRowSelect + ");");
		}
		
		// Add onCellClink handler if specified
		if (StringUtils.hasText(onCellClick)) {
			writeLine(" myDataTable.subscribe(\"cellClickEvent\", " + onCellClick + ");");
		}

		//Handles the scrolling to top on paging change
		/*writeLine("var pageRequest = false;");
		writeLine("myDataTable.get('paginator').on('changeRequest', function () {pageRequest = true;});");
		writeLine("myDataTable.on('renderEvent', function () {");
		writeLine("  if (pageRequest) {");
		writeLine("    pageRequest = false;");
        writeLine("    window.scroll(0,0);");
		writeLine("  }");
		writeLine("} );");*/
				
		// Make datatable, pager, & datasource available for clients under ID for this tag for later inspection
		writeLine("this." + id + " = {");
		writeLine("	pager  : myPager,");
		writeLine(" dataSource : myDataSource,");
		writeLine(" dataTable  : myDataTable");
		writeLine("};");
		
		// Update Payload data with values from server for tight integration.
		writeLine("myDataTable.doBeforeLoadData = function(oRequest, oResponse, oPayload) {");
		writeLine("  var defaultResultsPerPage = " + resultsPerPage + ";");
		writeLine("  var defaultSortColumn = '" + initialSortColumn + "';");
		// Meta *should* be defined but if not, create empty object and fall back to values specified for when DT was initialized.
		writeLine("  var meta = oResponse.meta || {};");
		writeLine("  oPayload.totalRecords = meta.totalRecords || 0; ");
		writeLine("  oPayload.pagination = {");
		writeLine("	   rowsPerPage: meta.paginationRowsPerPage || defaultResultsPerPage, ");
		writeLine("	   recordOffset: meta.paginationRecordOffset ? parseInt(meta.paginationRecordOffset) : 0 ");
		writeLine("  }; ");
		writeLine("  oPayload.sortedBy = {");
		writeLine("	   key: meta.sortKey || defaultSortColumn,");
		writeLine("	   dir: (meta.sortDir) ? 'yui-dt-' + meta.sortDir : 'yui-dt-asc'");
		writeLine("  };");
		writeLine("  return true; ");
	    writeLine("};");
		
		// Add a listener to a pageRefreshButton
		if (StringUtils.hasText(this.refreshPageButtonId)){
			writeLine("YAHOO.util.Event.addListener('" + refreshPageButtonId + "', 'click', function(e) {");
			writeLine("  var request = " + id + ".dataTable.get('generateRequest')(" + id + ".dataTable.getState(), myDataTable);");
			writeLine("  var oCallback = {");
			writeLine("    success: " + id + ".dataTable.onDataReturnSetRows,");
			writeLine("    failure: " + id + ".dataTable.onDataReturnSetRows,");
			writeLine("    argument: " + id + ".dataTable.getState(),");
			writeLine("    scope: " + id + ".dataTable");
			writeLine("  };");
			writeLine(   id + ".dataSource.sendRequest(request, oCallback);");
			writeLine("});");
		}
	    
		// Add handler button for triggering search (Will only pass rows per page, other state values are reset across searches)
		writeLine("YAHOO.util.Event.addListener('" + handlerButtonId + "', 'click', function(e) {");
		writeLine("  var rowsPerPage = " + id + ".dataTable.getState().pagination.rowsPerPage;");
		writeLine("  var request = " + id + ".dataTable.get('generateRequest')({pagination:{rowsPerPage: rowsPerPage}}, myDataTable);");
		writeLine("  var oCallback = {");
		writeLine("    success: " + id + ".dataTable.onDataReturnSetRows,");
		writeLine("    failure: " + id + ".dataTable.onDataReturnSetRows,");
		writeLine("    argument: " + id + ".dataTable.getState(),");
		writeLine("    scope: " + id + ".dataTable");
		writeLine("  };");
		writeLine(   id + ".dataSource.sendRequest(request, oCallback);");
		writeLine("});");
		
		writeLine("};");// End initDataTable  
		
		writeLine("initDataTable();");
		writeLine("</script>");
		
		JspWriter out = getJspContext().getOut();
		out.write(tagContent.toString());
			
	}
	
	private void initializeTagContent() {
		tagContent = new StringBuilder();
	}

	protected void writeLine(String line) {
		write(line + NEW_LINE);
	}
	
	protected void write(String value) {
		tagContent.append(value);
	}
	
	
	/**
	 * Private utility method used to invoke child column tags and return their 'cleaned' output.
	 * @param outputType The output type that the nested child tags should output (either column or schema).
	 * @return The markup generated from the nested child column tags with trailing comma from last tag removed.
	 * @throws JspException
	 * @throws IOException
	 */
	private String getChildMarkupByType(OutputType outputType) throws JspException, IOException {
		
		this.currentOutputType = outputType;
		StringWriter childOuput = new StringWriter();
		getJspBody().invoke(childOuput);
		// Remove trailing "," from child output
		StringBuilder sb = new StringBuilder(childOuput.toString().trim());
		sb.deleteCharAt(sb.length() - 1);
		
		return sb.toString();
		
	}
	
	@Override
	public OutputType getCurrentOutputType() {
		return currentOutputType;
	}

	public String getDataSourceUrl() {
		return dataSourceUrl;
	}

	public void setDataSourceUrl(String dataSourceUrl) {
		this.dataSourceUrl = dataSourceUrl;
	}

	public String getRequestBuilder() {
		return requestBuilder;
	}

	public void setRequestBuilder(String requestBuilder) {
		this.requestBuilder = requestBuilder;
	}

	public int getResultsPerPage() {
		return resultsPerPage;
	}

	public void setResultsPerPage(int resultsPerPage) {
		this.resultsPerPage = resultsPerPage;
	}

	public String getInitialSortColumn() {
		return initialSortColumn;
	}

	public void setInitialSortColumn(String defaultSortColumn) {
		this.initialSortColumn = defaultSortColumn;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getHandlerButtonId() {
		return handlerButtonId;
	}

	public void setHandlerButtonId(String handlerButtonId) {
		this.handlerButtonId = handlerButtonId;
	}

	public String getInitialSortOrder() {
		return initialSortOrder;
	}

	public void setInitialSortOrder(String initialSortOrder) {

		if ("ASC".equalsIgnoreCase(initialSortOrder)) {
			this.initialSortOrder = "YAHOO.widget.DataTable.CLASS_ASC";
		}

		else if ("DESC".equalsIgnoreCase(initialSortOrder)) {
			this.initialSortOrder = "YAHOO.widget.DataTable.CLASS_DESC";
		}

		else {
			log.warn("initialSortOrder of '" + initialSortOrder + "' not supported, using default " + DEFAULT_SORT_ORDER);
			this.initialSortOrder = DEFAULT_SORT_ORDER;
		}

	}

	public String getTotalRecordsField() {
		return totalRecordsField;
	}

	public void setTotalRecordsField(String totalRecordsField) {
		this.totalRecordsField = totalRecordsField;
	}

	public String getDoBeforeParseDataCallback() {
		return doBeforeParseDataCallback;
	}

	public void setDoBeforeParseDataCallback(String doBeforeParseDataCallback) {
		this.doBeforeParseDataCallback = doBeforeParseDataCallback;
	}

	public String getRefreshPageButtonId() {
		return refreshPageButtonId;
	}

	public void setRefreshPageButtonId(String refreshPageButtonId) {
		this.refreshPageButtonId = refreshPageButtonId;
	}

	public String getPreviousPageLinkLabel() {
		return previousPageLinkLabel;
	}

	public void setPreviousPageLinkLabel(String previousPageLinkLabel) {
		this.previousPageLinkLabel = previousPageLinkLabel;
	}

	public String getNextPageLinkLabel() {
		return nextPageLinkLabel;
	}

	public void setNextPageLinkLabel(String nextPageLinkLabel) {
		this.nextPageLinkLabel = nextPageLinkLabel;
	}

	public String getResultListField() {
		return resultListField;
	}

	public void setResultListField(String resultListField) {
		this.resultListField = resultListField;
	}

	public String getStartIndexField() {
		return startIndexField;
	}

	public void setStartIndexField(String startIndexField) {
		this.startIndexField = startIndexField;
	}

	public String getPageSizeField() {
		return pageSizeField;
	}

	public void setPageSizeField(String pageSizeField) {
		this.pageSizeField = pageSizeField;
	}

	public String getSortColumnField() {
		return sortColumnField;
	}

	public void setSortColumnField(String sortColumnField) {
		this.sortColumnField = sortColumnField;
	}

	public String getSortDirectionField() {
		return sortDirectionField;
	}

	public void setSortDirectionField(String sortDirectionField) {
		this.sortDirectionField = sortDirectionField;
	}

	public String getStatusField() {
		return statusField;
	}

	public void setStatusField(String statusField) {
		this.statusField = statusField;
	}

	public boolean isDisplayRowsPerPageDropDown() {
		return displayRowsPerPageDropDown;
	}

	public void setDisplayRowsPerPageDropDown(boolean displayRowsPerPageDropDown) {
		this.displayRowsPerPageDropDown = displayRowsPerPageDropDown;
	}

	public String getRowsPerPageOptions() {
		return rowsPerPageOptions;
	}

	public void setRowsPerPageOptions(String rowsPerPageOptions) {
		this.rowsPerPageOptions = rowsPerPageOptions;
	}

	public String getInitialLoad() {
		return initialLoad;
	}

	public void setInitialLoad(String initialLoad) {
		this.initialLoad = initialLoad;
	}

	public String getInitialRequest() {
		return initialRequest;
	}

	public void setInitialRequest(String initialRequest) {
		this.initialRequest = initialRequest;
	}

	public String getRowFormatterCallback() {
		return rowFormatterCallback;
	}

	public void setRowFormatterCallback(String rowFormatterCallback) {
		this.rowFormatterCallback = rowFormatterCallback;
	}

	public String getOnCellClick() {
		return onCellClick;
	}

	public void setOnCellClick(String onCellClick) {
		this.onCellClick = onCellClick;
	}

	public String getOnRowSelect() {
		return onRowSelect;
	}

	public void setOnRowSelect(String onRowSelect) {
		this.onRowSelect = onRowSelect;
	}

	public String getPageReportTemplate() {
		return pageReportTemplate;
	}

	public void setPageReportTemplate(String pageReportTemplat) {
		this.pageReportTemplate = pageReportTemplat;
	}
		
}
